package com.gcorp.idealgas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.ListIterator;

import javax.swing.JPanel;
import javax.swing.Timer;

import com.gcorp.idealgas.type.StandardMoleculeWithRandomSpeed;


/**
 * This class contains simulation needs such as array with balls,
 * array with baffles, delay time etc. It provides methods as 
 * start(), stop(), getMouseX(), getMouseY() and much much others of nishtyaki
 * @author George Mays
 *
 */
public class Simulation extends JPanel implements ActionListener,MouseListener
{
    private static Simulation instance;
    
    private static final int timerDelay = 10;
    
    private static final float updateDelay = timerDelay / 10;
    
    private Timer timer;
    
    private float timeCounter;
    
    private ArrayList<Molecule> Moleculas;
    
    private ArrayList<Baffle> customBaffles;
    
    private Rectangle simRect;
    
    

    

    private Simulation()
    {
        initializeFocus();
        initializeWindowSize();
        initializeMoleculas();
        initializeBaffles();
        initializeTimer();
        
    }
    public static Simulation getInstance()
    {
        if(instance == null)
        {
            instance = new Simulation();
        }
        
        return instance;
    }
    
    

    @Override
    public void actionPerformed(ActionEvent e) 
    {
        // TODO: Here are new coords are calculating
        
        for(ListIterator<Molecule> it = Moleculas.listIterator();it.hasNext();)
        {
            Molecule m = it.next();
            m.update();
            it.set(m);
        }
        
        repaint();
        
    }

    
    @Override
    public void paintComponent(Graphics g) 
    {
        super.paintComponent(g);
        
        Graphics2D g2d = (Graphics2D)g;

        updateMoleculas(g2d);
        
        drawCustomCollisionShapes(g2d);
     
    }
    
    private void updateMoleculas(Graphics2D g2d)
    {
    	for(ListIterator<Molecule> it = Moleculas.listIterator(); it.hasNext();)
        {
    		Molecule m = it.next();
            updateSingleMolecula(m,g2d);
            it.set(m);
        }
    }
    private void updateSingleMolecula(Molecule m,Graphics2D g2d)
    {
    	myPoint move = calculateMovement(m);

        drawSingleMolecula(m,g2d,move);
            
        detectCollisions(m);
           
        
    }
    private void drawSingleMolecula(Molecule m,Graphics2D g2d,myPoint move)
    {
        m.draw(g2d);
    }
    
    private myPoint calculateMovement(Molecule m)
    {
        myPoint move = m.move();
        if((move.getX() == 0) && (move.getY() == 0))
        {
            return m.getInitialPosition();
        }
        else
        {
        	m.setCurrentPosition(m.getInitialPosition().add(move));
            return m.getCurrentPosition();
        }
        
    }

    private void checkBallsCollisions()
    {
        for(ListIterator<Molecule> it = Moleculas.listIterator();it.hasNext();)
        {
            Molecule mol1 = it.next();
            
            for(ListIterator<Molecule> jt = Moleculas.listIterator();jt.hasNext();)
            {
               Molecule mol2 = jt.next();
               
               float d1 = mol2.getCurrentCenter().getY()-mol1.getCurrentCenter().getY();
               float d2 = mol2.getCurrentCenter().getX()-mol1.getCurrentCenter().getX();
               
               float dim = (float) Math.sqrt(d1*d1 + d2*d2);
               float rad = (mol1.getRadius() + mol2.getRadius());
               if(dim >= rad && (mol1.getBouncedMolecule() == mol2 && mol2.getBouncedMolecule() == mol1) &&  (mol1.isBounced() && mol2.isBounced()))
               {
                   mol1.setUnbounced();
                   mol2.setUnbounced();
                  
               }
               if(dim  <  rad && dim != 0 && (!mol1.isBounced() || !mol2.isBounced()))
               {
                   //System.out.println("Collision detected");
                   
                   
                   Velocity[] Velocities = calculateVelocityAfterCollision(mol1,mol2);
                   
                   
                   
                   mol1.setdVx(Velocities[0].getX());
                   mol1.setdVy(Velocities[0].getY());
                   
                   //System.out.println("Past Vx = " + Vx1 + ", Vy = " + Vy1 + " : new Vx = " + ffVx1 + ", Vy = " + ffVy1);
                   if(Velocities[1].getX() != 0.0f && Velocities[1].getY() != 0.0f)
                   {
                       mol2.setdVx(Velocities[1].getX());
                       mol2.setdVy(Velocities[1].getY());
                   }
                   

                   
                   mol1.reset();
                   mol1.setBounced(mol2);
                   mol1.setInitialPosition(mol1.getCurrentPosition().add(mol1.move(1.5f)));
                   
                   mol2.reset();
                   mol2.setBounced(mol1);
                   mol2.setInitialPosition(mol2.getCurrentPosition().add(mol2.move(1.5f)));
               }
               
               
               
               jt.set(mol2); 
               it.set(mol1);
              
            }
            
        }
    }
    private double getAngle(float dx,float dy)
    {
        return Math.atan2(dy, dx);
    }
    private double getMagnitude(float Vx,float Vy)
    {
        return Math.sqrt(Vx * Vx + Vy * Vy);
    }
    private double getDirection(float Vx,float Vy)
    {
        return Math.atan2(Vy, Vx);
    }
    private double getNewVx(double magnitude, double direction, double angle)
    {
        return magnitude * Math.cos(direction-angle);
    }
    private double getNewVy(double magnitude, double direction, double angle)
    {
        return magnitude * Math.sin(direction-angle);
    }
    private Velocity[] calculateVelocityAfterCollision(Molecule mol1, Molecule mol2)
    {
       float Vx1 = mol1.getVelocity().getX();
       float Vy1 = mol1.getVelocity().getY();
       float x1  = mol1.getCurrentCenter().getX();
       float y1  = mol1.getCurrentCenter().getY();
       float m1  = mol1.getMass();
       
       float Vx2 = mol2.getVelocity().getX();
       float Vy2 = mol2.getVelocity().getY();
       float x2  = mol2.getCurrentCenter().getX();
       float y2  = mol2.getCurrentCenter().getY();
       float m2  = mol2.getMass();
       
       float dx = x1 - x2;
       float dy = y1 - y2;
       
       double angle = getAngle(dx,dy);
       
       double magn1 = getMagnitude(Vx1,Vy1);
       double magn2 = getMagnitude(Vx2,Vy2);
       
       double dir1  = getDirection(Vx1,Vy1);
       double dir2  = getDirection(Vx2,Vy2);
       
       double newVx1 =  getNewVx(magn1,dir1,angle);
       double newVy1 =  getNewVy(magn1,dir1,angle);   
       double newVx2 =  getNewVx(magn2,dir2,angle);
       double newVy2 =  getNewVy(magn2,dir2,angle);
       
       double finalVx1 = ((m1 - m2) * newVx1 + (m2*m2)   * newVx2) / (m1 + m2);
       double finalVx2 = ((m1 + m1) * newVx1 + (m2 - m1) * newVx2) / (m1 + m2);
       double finalVy1 = newVy1;
       double finalVy2 = newVy2;
       
       float ffVx1 = (float) (Math.cos(angle) * finalVx1 + Math.cos(angle) * finalVy1);
       float ffVy1 = (float) (Math.sin(angle) * finalVx1 + Math.sin(angle) * finalVy1);
       
       float ffVx2 = (float) (Math.cos(angle) * finalVx2 + Math.cos(angle) * finalVy2);
       float ffVy2 = (float) (Math.sin(angle) * finalVx2 + Math.sin(angle) * finalVy2);
       
           
       
       
       Velocity[] result = new Velocity[2];
       
       result[0] = new Velocity(ffVx1,ffVy1);
       result[1] = new Velocity(ffVx2,ffVy2);
       
       return result;
    }
    
    
    private void detectCollisions(Molecule m)
    {
    	checkBallsCollisions();
    	checkCustomCollisions(m);
    	
    
    }
    private void checkCustomCollisions(Molecule m)
    {
        myPoint currPos = m.move(2.6f);
        currPos = m.getInitialPosition().add(currPos);
        for(ListIterator<Baffle> it = customBaffles.listIterator();it.hasNext();)
        {
            Baffle b = it.next();
            
            if(b.isCollised(m, currPos))
            {
                b.setColor(Color.GREEN);
                if(b.isXReversed(m))
                {
                    m.reverseX();
                }
                else if(b.isYReversed(m))
                {
                    m.reverseY();
                }
                b.setColor(Color.BLACK);
                m.reset();
            }
        }
    }
    private void drawCustomCollisionShapes(Graphics2D g2d)
    {
        for(ListIterator<Baffle> it = customBaffles.listIterator();it.hasNext();)
        {
            Baffle b = it.next();
            
            b.draw(g2d);
        }
    }
    public void start()
    {
        timer.start();
    }
    public void stop()
    {
        timer.stop();
    }
    public void restart()
    {
    	timer.stop();
    	Moleculas.clear();
    	initializeMoleculas();
    	
    	initializeTimer();
    	repaint();
    	timer.start();
    }
    
	public void initializeFocus()
	{
	    setFocusable(true);
	    addMouseListener(this);
	}
	public void initializeTimer()
	{
	    timer = new Timer(timerDelay,this);
	}
	public synchronized void addNewMolecule(Molecule m)
	{
	    Moleculas.add(m);
	}
	public void redraw()
	{
	    repaint();
	}
	public void initializeMoleculas()
	{
	    Moleculas = new ArrayList<Molecule>();

	    for(int i =0; i<20;i++)
	    {
	    	Moleculas.add(new Molecule(60,60+i*15,new StandardMoleculeWithRandomSpeed(),updateDelay));
	    }
	}
	public void initializeBaffles()
	{
	    customBaffles = new ArrayList<Baffle>();
        

        customBaffles.add(new LineBaffle(simRect.x,simRect.y,simRect.width,simRect.y,true));
        customBaffles.add(new LineBaffle(simRect.width,simRect.y, simRect.width,simRect.height,false));
        customBaffles.add(new LineBaffle(simRect.width,simRect.height, simRect.x,simRect.height,true));
        customBaffles.add(new LineBaffle(simRect.x,simRect.height,simRect.x,simRect.y,false));
        customBaffles.add(new LineBaffle(100,100,200,100,true));
	}
	public void initializeWindowSize()
	{
	    simRect = new Rectangle(0,0,500,500);
	
	}
	@Override
    public void mouseClicked(MouseEvent arg0) 
    {
        
    }
    @Override
    public void mouseEntered(MouseEvent arg0) 
    {
        
    }
    @Override
    public void mouseExited(MouseEvent arg0) 
    {
        
    }
    @Override
    public void mousePressed(MouseEvent arg0) 
    {
        //System.out.println("X = " + arg0.getX() + ", Y = " + arg0.getY());

        
        if(AddNewMoleculeDialog.getInstance().isWaitForCoordsState())
        {
            AddNewMoleculeDialog.getInstance().setX(arg0.getX());
            AddNewMoleculeDialog.getInstance().setY(arg0.getY());
            AddNewMoleculeDialog.getInstance().setWaitingForCoordsState(false);
            AddNewMoleculeDialog.getInstance().showWindow();
        }
    }
    @Override
    public void mouseReleased(MouseEvent arg0) 
    {
        
    }
}